﻿using System;
using System.Collections.ObjectModel;
using Microsoft.LiveFX.ResourceModel;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Wintellect.LiveFx;
using System.Windows.Media.Imaging;

namespace Wintellect.LiveFx.Cloudboard
{
  public class Clipboard
  {
    public String Name { get; private set; }
    public String Description { get; set; }
    public DateTimeOffset Created { get; set; }
    public DateTimeOffset LastUpdated { get; set; }

    private bool offline;
    private DataAccessLayer dataLayer;
    private List<ClipboardItem> clipboardItems;

    public event EventHandler<ItemsChangedEventArgs> ItemsChanged;
    public event EventHandler OfflineChanged;

    public Clipboard(String name)
    {
      EtwTrace.Trace(EtwTraceLevel.Verbose, EtwTraceFlag.Diagnostic, "Creating Clipboard object '{0}'.", name);
      this.Name = name;
      this.clipboardItems = new List<ClipboardItem>();
      this.offline = true;
    }

    public bool Offline
    {
      get
      {
        return this.offline;
      }
      private set
      {
        if (this.offline != value)
        {
          EtwTrace.Trace(EtwTraceLevel.Information, EtwTraceFlag.Diagnostic, "Offline status changing to {0}", this.offline);
          this.offline = value;
          this.OnOfflineChanged();
        }
      }
    }

    private void OnOfflineChanged()
    {
      if (this.OfflineChanged != null)
      {
        this.OfflineChanged(this, new EventArgs());
      }
    }

    public void GoOnline()
    {
      try
      {
        EtwTrace.Trace(EtwTraceLevel.Verbose, EtwTraceFlag.Diagnostic, "Going online...");
        this.dataLayer = new DataAccessLayer(this);
        this.dataLayer.OfflineChanged += (o, e) => { this.Offline = this.dataLayer.Offline; };
        this.dataLayer.ItemsChanged += (o, e) => { this.SynchClipboardItems(); };
        this.Offline = false;
        this.SynchClipboardItems();
      }
      catch (LiveFxException<UnableToConnectToLoe> connectionException)
      {
        EtwTrace.Trace(EtwTraceLevel.Error, EtwTraceFlag.Diagnostic, "Exception encountered while going online: {0}", connectionException);
        this.Offline = true;
      }
      catch (LiveFxException<UnableToAccessLoeData> unableToAccessException)
      {
        EtwTrace.Trace(EtwTraceLevel.Error, EtwTraceFlag.Diagnostic, "Exception encountered while going online: {0}", unableToAccessException);
        this.Offline = true;
      }
    }

    public void Delete()
    {
      if (this.offline) this.GoOnline();

      this.dataLayer.DeleteMeshObject();
    }

    public void Update()
    {
      this.GoOnline();
    }

    public String LiveID
    {
      get
      {
        if (this.offline) this.GoOnline();

        return this.dataLayer.Mesh.ProvisionedUser.Email;
      }
    }

    public IList<MemberRegistration> Members
    {
      get
      {
        if (this.offline) this.GoOnline();

        List<MemberRegistration> output = null;
        if (!this.offline)
        {
          output = new List<MemberRegistration>(
            from m in this.dataLayer.Members
            select new MemberRegistration()
            {
              Id = m.Id,
              Email = m.Email,
              Role = (Role)m.Role,
              Existing = true,
              InvitationStatus = GetInvitationStatus(m)
            });
          output.Add(new MemberRegistration());
        }
        return output;
      }
    }

    private static InvitationStatus GetInvitationStatus(MemberResource member)
    {
      if (member.IsOwner) return InvitationStatus.Owner;
      if (member.InvitationAccepted) return InvitationStatus.Accepted;
      if (member.PendingInvitation != null && member.PendingInvitation.Expires > DateTime.Now)
      {
        return InvitationStatus.Pending;
      }
      else
      {
        return InvitationStatus.Expired;
      }
    }

    public void SynchMembers(IList<MemberRegistration> members)
    {
      if (this.offline) this.GoOnline();

      if (this.offline) return;

      foreach (MemberRegistration member in members)
      {
        if (!member.Existing && !String.IsNullOrEmpty(member.Email) && member.Role != Role.Remove)
        {
          // Time to invite a new member!
          Invitation invitation = new Invitation();
          invitation.Expires = DateTimeOffset.UtcNow.Add(new TimeSpan(1, 0, 0));
          invitation.Email = string.Format(
            @"{0} would like to share one of his Live! clipboards with you. Would you like to accept?

If you haven't already installed Cloudboard, you can download it from http://demo.wintellect.com/GetCloudboard",
            this.dataLayer.GetProvisionedUserName());

          MemberResource newMember = new MemberResource(member.Email, (RoleType)member.Role, true);
          newMember.PendingInvitation = invitation;

          this.dataLayer.AddMember(newMember);
        }
        if (member.Existing && member.Role == Role.Remove)
        {
          MemberResource existingMember = (from m in this.dataLayer.Members where m.Email == member.Email select m).First();
          this.dataLayer.DeleteMember(existingMember);
        }
      }
    }

    public void AddClipboardItem(ClipDataInfo cdi, Stream dataStream)
    {
      if (this.offline) this.GoOnline();

      if (this.offline) return;
      DataEntryResource dataentry = this.dataLayer.AddDataEntry(cdi, dataStream);
    }

    public IList<ClipboardItem> ClipboardItems
    {
      get
      {
        if (this.offline) this.GoOnline();
        return this.clipboardItems.AsReadOnly();
      }
    }

    public void DeleteItem(ClipboardItem item)
    {
      if (this.offline) this.GoOnline();

      if (this.offline) return;
      this.dataLayer.DeleteDataEntry(item.Resource);
    }

    private void SynchClipboardItems()
    {
      if (this.offline) this.GoOnline();

      if (this.offline) return;

      IList<ClipboardItem> added = new List<ClipboardItem>();
      IList<ClipboardItem> removed = new List<ClipboardItem>();
      bool changed = false;
      // Synch up the local list
      IList<ClipboardItem> foundItems = this.dataLayer.Items;
      foreach (ClipboardItem item in foundItems)
      {
        var localMatch = (from le in this.clipboardItems where le.Resource.Id == item.Resource.Id select le).FirstOrDefault();
        if (localMatch == null && item.Resource.IsMediaLinkEntry)
        {
          changed = true;
          ClipboardItem newItem = new ClipboardItem();
          newItem.Resource = item.Resource;
          newItem.Media = this.dataLayer.ReadMediaResource(item.Resource);
          added.Add(newItem);
          this.clipboardItems.Add(newItem);
        }
      }
      IList<ClipboardItem> toRemove = new List<ClipboardItem>();
      for (int index = this.clipboardItems.Count - 1; index >= 0; index--)
      {
        ClipboardItem existingItem = this.clipboardItems[index];
        var cloudMatch = (from le in foundItems where le.Resource.Id == existingItem.Resource.Id select le).FirstOrDefault();
        if (cloudMatch == null)
        {
          changed = true;
          removed.Add(existingItem);
          this.clipboardItems.RemoveAt(index);
        }
      }

      if (changed) this.OnItemsChanged(added, removed);
    }

    private void OnItemsChanged(IList<ClipboardItem> added, IList<ClipboardItem> removed)
    {
      if (this.ItemsChanged != null)
      {
        this.ItemsChanged(this, new ItemsChangedEventArgs(added, removed));
      }
    }

    public void AddItemFromSystemClipboard()
    {
      ClipDataInfo cdi = default(ClipDataInfo);

      using (MemoryStream ms = new MemoryStream())
      {
        if (System.Windows.Forms.Clipboard.ContainsText())
        {
          cdi =
             (from cd in ClipboardHelper.s_ClipData
              where cd.DataType == ClipDataType.Text && System.Windows.Clipboard.ContainsData(cd.Format)
              select cd).First();

          String text = (String)System.Windows.Clipboard.GetData(cdi.Format);
          Byte[] bytes = System.Text.Encoding.UTF8.GetBytes(text);
          ms.Write(bytes, 0, bytes.Length);
        }
        else if (System.Windows.Clipboard.ContainsImage())
        {
          cdi =
             (from cd in ClipboardHelper.s_ClipData
              where cd.DataType == ClipDataType.Bitmap && System.Windows.Clipboard.ContainsData(cd.Format)
              select cd).First();
          BitmapSource bitmap = System.Windows.Clipboard.GetImage();
          BitmapEncoder encoder = new JpegBitmapEncoder();
          encoder.Frames.Add(BitmapFrame.Create(bitmap));
          encoder.Save(ms);
        }
        else
        {
          // Not much we can do here; we currently only store/display text and images
        }

        ms.Seek(0, SeekOrigin.Begin);

        if (!String.IsNullOrEmpty(cdi.FileExtension))
        {
          this.AddClipboardItem(cdi, ms);
        }
        else
        {
          // TODO: Log inability to capture data
        }
      }
    }

    public void CopyItemToSystemClipboard(int index)
    {
      if (index < 0 || index > this.clipboardItems.Count - 1)
      {
        throw new ArgumentOutOfRangeException("index");
      }

      Object item = this.clipboardItems[index].Media;
      System.Windows.Media.Imaging.BitmapSource imageItem = item as System.Windows.Media.Imaging.BitmapSource;
      String textItem = item as String;

      if (textItem != null)
      {
        System.Windows.TextDataFormat format;
        if (MeshHelper.IsTextRTF(textItem))
        {
          format = System.Windows.TextDataFormat.Rtf;
        }
        else
        {
          format = System.Windows.TextDataFormat.Text;
        }
        System.Windows.Clipboard.SetText(textItem, format);
      }
      else if (imageItem != null)
      {
        System.Windows.Clipboard.SetImage(imageItem);
      }
      else
      {
        System.Windows.Clipboard.SetText(item.ToString());
      }
    }
  }
}
